Where
-Infinity
0
Severity
6.5
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

The Mbed TLS crypto shim driver in drivers/crypto/cryptombedtlsshim.c drives PSA multipart cipher and AEAD operations. In mbedtlscbc() and mbedtlsaead(), every error path after a successful psacipherencryptsetup() / psaaeaddecryptsetup() returned directly without calling psacipherabort() / psaaeadabort(). Because the abort is the only thing that reaches mbedtlscipherfree(), each failed operation permanently leaked the heap-allocated mbedtlsaescontext (roughly 288 bytes) that setup had allocated from Mbed TLS's fixed static heap (mbedtlsheap[CONFIGMBEDTLSHEAPSIZE], default 512 bytes). With CONFIGPSACRYPTOPROVIDERTFM the leaked object is instead a finite secure-partition multipart-operation slot.

The failure is chosen by an unauthenticated attacker. With CONFIGNETL2IEEE802154SECURITY backed by this driver, ieee802154decryptauth() in subsys/net/l2/ieee802154/ieee802154security.c invokes cipherccmop() on every received secured frame, with no authentication ahead of the MIC check. A device in radio range can transmit a stream of syntactically valid 802.15.4 frames carrying a bogus MIC; each one makes psaaeadverify() fail and leaks a block. The CBC path is reachable the same way through the OSDP secure channel, where CONFIGOSDPSCENABLED selects this shim and osdpencrypt() / osdpdecrypt() leave outbufmax unset, so every operation fails inside psacipherupdate().

Exhausting the Mbed TLS heap — which the default configuration reaches after a couple of failed operations, and any realistic configuration reaches in well under a second of frames — makes all subsequent mbedtlscalloc() calls fail. Every later cryptographic operation in the image then fails permanently, including link-layer security and any TLS/DTLS sharing the same heap, until the device is rebooted. There is no memory corruption or information disclosure: frames with an invalid MIC are still rejected, so the impact is limited to availability.

The fix routes all post-setup failures in mbedtlscbc() and mbedtlsaead() through a common error: label that calls the matching PSA abort before returning. Setup failures need no change, since the PSA core aborts the operation itself when setup fails.

First published (updated )
Severity
6.5
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

The Mbed TLS crypto shim driver in drivers/crypto/cryptombedtlsshim.c drives PSA multipart cipher and AEAD operations. In mbedtlscbc() and mbedtlsaead(), every error path after a successful psacipherencryptsetup() / psaaeaddecryptsetup() returned directly without calling psacipherabort() / psaaeadabort(). Because the abort is the only thing that reaches mbedtlscipherfree(), each failed operation permanently leaked the heap-allocated mbedtlsaescontext (roughly 288 bytes) that setup had allocated from Mbed TLS's fixed static heap (mbedtlsheap[CONFIGMBEDTLSHEAPSIZE], default 512 bytes). With CONFIGPSACRYPTOPROVIDERTFM the leaked object is instead a finite secure-partition multipart-operation slot.

The failure is chosen by an unauthenticated attacker. With CONFIGNETL2IEEE802154SECURITY backed by this driver, ieee802154decryptauth() in subsys/net/l2/ieee802154/ieee802154security.c invokes cipherccmop() on every received secured frame, with no authentication ahead of the MIC check. A device in radio range can transmit a stream of syntactically valid 802.15.4 frames carrying a bogus MIC; each one makes psaaeadverify() fail and leaks a block. The CBC path is reachable the same way through the OSDP secure channel, where CONFIGOSDPSCENABLED selects this shim and osdpencrypt() / osdpdecrypt() leave outbufmax unset, so every operation fails inside psacipherupdate().

Exhausting the Mbed TLS heap — which the default configuration reaches after a couple of failed operations, and any realistic configuration reaches in well under a second of frames — makes all subsequent mbedtlscalloc() calls fail. Every later cryptographic operation in the image then fails permanently, including link-layer security and any TLS/DTLS sharing the same heap, until the device is rebooted. There is no memory corruption or information disclosure: frames with an invalid MIC are still rejected, so the impact is limited to availability.

The fix routes all post-setup failures in mbedtlscbc() and mbedtlsaead() through a common error: label that calls the matching PSA abort before returning. Setup failures need no change, since the PSA core aborts the operation itself when setup fails.

First published (updated )
Severity
4.8
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N

The entropy-device random number generator in subsys/random/randomentropydevice.c returned early with -ENODEV from randget() as soon as deviceisready() failed, before reaching the non-cryptographic system-timer fallback further down the same function. Because zimplsysrandget() returns void and discards that error — and the public sysrandget() syscall is itself declared void, so it cannot report failure — the caller's output buffer was left untouched and its previous contents were handed back as random data. The thin wrappers make this a plain uninitialized read: sysrand32get() declares uint32t ret;, calls sysrandget(&ret, sizeof(ret)) and returns ret.

The "not ready" state is a normal runtime condition rather than a fault. Entropy drivers initialise at PREKERNEL1 with CONFIGENTROPYINITPRIORITY (default 50), so any earlier initialiser — kernel objects run at priority 30 — observes an unready device; the sibling generator subsys/random/randomxoshiro128.c and zearlyrandget() in kernel/init.c both already carried an explicit timer fallback for exactly this case. The state is also permanent for the boot when an entropy driver's init hook fails, which several in-tree drivers can do on real hardware (entropystm32 on an RNG clock configuration error, entropypsacrypto when psacryptoinit() fails, entropynrfcracen, entropyesp32, entropyvirtio).

When this occurs, every non-cryptographic random value in the system is either a constant or stale memory. coapnexttoken() in subsys/net/lib/coap/coap.c fills a static buffer, so every CoAP token becomes the same .bss constant; tcpinitisn() in subsys/net/ip/tcp.c returns an uninitialized stack word as the TCP initial sequence number; DHCPv4/DHCPv6 transaction IDs, WebSocket masking keys and RTP SSRCs are similarly degraded, and arch/arm64/core/pac.c derives per-thread Pointer Authentication keys from the same source. Where the bytes are transmitted directly — SSH packet padding, the netmidi2 session nonce, a DTLS connection ID on builds without CONFIGCSPRNGENABLED — stale caller stack or heap contents are disclosed on the wire.

The fix initialises ret to -ENODEV and only calls entropygetentropy() when the device is ready, so the not-ready case now falls through to the timer fallback and the buffer is always written. The cryptographically secure path is unchanged: syscsrandget() still propagates the error to the caller and never silently degrades. An attacker cannot induce the unready state, but can observe and exploit the resulting non-random values remotely on an affected device.

First published (updated )
Severity
7.8
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The RTIO submission-queue verifier rtiovrfysqe() in subsys/rtio/rtiosyscalls.c (subsys/rtio/rtiohandlers.c before v4.3.0) accumulated the results of KSYSCALLMEMORY() into its validsqe flag with &=. That macro returns nonzero on failure (it is built on KSYSCALLVERIFYMSG(), which yields !(expr)), so every buffer permission check contributed the opposite of its result. A submission whose tx.buf, rx.buf or txrx.txbuf/txrx.rxbuf pointer was not accessible to the calling thread was accepted, while a legitimate, thread-owned buffer caused the calling thread to be killed with a kernel oops.

The verifier runs on behalf of the rtiosqecopyingethandles() system call (also reached through the inline rtiosqecopyin() wrapper), so it is driven directly by untrusted user-mode input on any image built with CONFIGUSERSPACE. A user thread that has been granted an RTIO context and an rtioiodev object — the intended usage of RTIO userspace support, established via rtioaccessgrant() — can submit an SQE whose buffer pointer and length name arbitrary kernel memory; the object check on sqe->iodev is unaffected and still passes, and the crafted SQE is forwarded to the bus driver unchanged.

Downstream consumers dereference the pointer with supervisor privilege. The generic I2C and I3C RTIO fallback handlers (drivers/i2c/i2crtiodefault.c, drivers/i3c/i3crtiodefault.c) copy sqe.rx.buf/sqe.tx.buf into struct i2cmsg and call i2ctransfer(), and the vendor SPI/I2C RTIO drivers behave equivalently. An RTIOOPRX submission therefore has peripheral data written into kernel memory of the attacker's choosing (arbitrary kernel write), an RTIOOPTX submission clocks kernel memory out over the bus (kernel memory disclosure), and a pointer to unmapped memory faults the kernel. This is a full escape from the user-mode isolation boundary that CONFIGUSERSPACE exists to enforce.

The defect was masked for years because the in-tree user-mode RTIO tests only exercise RTIOOPNOP and mempool-backed reads, neither of which reaches the affected checks. The fix negates each KSYSCALLMEMORY() result and additionally validates the TXRX transmit buffer read-only, matching the SPI, I2C and I3C handlers.

First published (updated )
Severity
8.3
AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H

The nRF link-layer receive paths of the open-source Bluetooth LE Controller did not validate the on-air LENGTH field of a received PDU against the maximum receive length that was used to program the radio. isrrxpdu() in subsys/bluetooth/controller/llsw/nordic/lll/lllconn.c enqueued any non-empty data PDU after only the sequence-number and (for encrypted links) MIC checks, and the corresponding isrrx() handlers in lllcentraliso.c, lllperipheraliso.c, lllsynciso.c and lllscanaux.c did the same for ISO and extended-advertising PDUs. The nRF radio stores the received header verbatim and truncates only the payload at PCNF1.MAXLEN, so a frame declaring more octets than were configured leaves a node buffer whose len field describes more data than the buffer holds.

An attacker within radio range that can emit a hand-crafted link-layer PDU — for a connection, any peer that establishes an unencrypted LE connection to a connectable advertiser; for CONFIGBTCTLRSYNCISO, any broadcaster the device is synchronised to — can set the length byte up to 255 while the controller has programmed a much smaller maximum (lll->dle.eff.maxrxoctets, which is 27 until a Data Length Update, or the negotiated cislll->rx.maxpdu). No pairing, authentication or user interaction is required. On encrypted links the CCM MIC check normally rejects such a frame first, so the exposure is on unencrypted traffic, which is why the added check is placed after decryption.

The unchecked length reaches hciaclencode() in subsys/bluetooth/controller/hci/hci.c, which performs netbufadd(buf, pdudata->len) followed by a memcpy() of that many bytes out of the noderx pool element. Because CONFIGBTCTLRDATALENGTHMAX and CONFIGBTBUFACLRXSIZE both default to 27, this is an out-of-bounds read of up to roughly 228 bytes past the received PDU and a corresponding out-of-bounds write past the HCI ACL buffer into neighbouring buffers of the same pool; the tailroom test in netbufsimpleadd() is only enforced when CONFIGNETBUFHARDENING (or CONFIGASSERT) is enabled. On the ISO paths the same unchecked length makes isoalrxappendtosdu() read past the ISO node buffer and emit adjacent controller RAM to the host as ISO data. The practical result is remote memory corruption and controller crashes, plus disclosure of controller memory into the host data path.

The fix rejects any received PDU whose length exceeds the value that was used to configure the radio reception. Note that lllsync.c (periodic advertising sync reception) was not changed and still enqueues received PDUs without an equivalent bound.

First published (updated )
Severity
8.3
AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H

The nRF link-layer receive paths of the open-source Bluetooth LE Controller did not validate the on-air LENGTH field of a received PDU against the maximum receive length that was used to program the radio. isrrxpdu() in subsys/bluetooth/controller/llsw/nordic/lll/lllconn.c enqueued any non-empty data PDU after only the sequence-number and (for encrypted links) MIC checks, and the corresponding isrrx() handlers in lllcentraliso.c, lllperipheraliso.c, lllsynciso.c and lllscanaux.c did the same for ISO and extended-advertising PDUs. The nRF radio stores the received header verbatim and truncates only the payload at PCNF1.MAXLEN, so a frame declaring more octets than were configured leaves a node buffer whose len field describes more data than the buffer holds.

An attacker within radio range that can emit a hand-crafted link-layer PDU — for a connection, any peer that establishes an unencrypted LE connection to a connectable advertiser; for CONFIGBTCTLRSYNCISO, any broadcaster the device is synchronised to — can set the length byte up to 255 while the controller has programmed a much smaller maximum (lll->dle.eff.maxrxoctets, which is 27 until a Data Length Update, or the negotiated cislll->rx.maxpdu). No pairing, authentication or user interaction is required. On encrypted links the CCM MIC check normally rejects such a frame first, so the exposure is on unencrypted traffic, which is why the added check is placed after decryption.

The unchecked length reaches hciaclencode() in subsys/bluetooth/controller/hci/hci.c, which performs netbufadd(buf, pdudata->len) followed by a memcpy() of that many bytes out of the noderx pool element. Because CONFIGBTCTLRDATALENGTHMAX and CONFIGBTBUFACLRXSIZE both default to 27, this is an out-of-bounds read of up to roughly 228 bytes past the received PDU and a corresponding out-of-bounds write past the HCI ACL buffer into neighbouring buffers of the same pool; the tailroom test in netbufsimpleadd() is only enforced when CONFIGNETBUFHARDENING (or CONFIGASSERT) is enabled. On the ISO paths the same unchecked length makes isoalrxappendtosdu() read past the ISO node buffer and emit adjacent controller RAM to the host as ISO data. The practical result is remote memory corruption and controller crashes, plus disclosure of controller memory into the host data path.

The fix rejects any received PDU whose length exceeds the value that was used to configure the radio reception. Note that lllsync.c (periodic advertising sync reception) was not changed and still enqueues received PDUs without an equivalent bound.

First published (updated )
Severity
7.8
AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H

The user-mode syscall verifier zvrfyi2sconfigure() in drivers/i2s/i2shandlers.c copies the caller's struct i2sconfig in from user memory and then, when cfg->frameclkfreq is 0 (the API's "unconfigure the stream" request), jumps straight to zimpli2sconfigure() without performing the KSYSCALLOBJ(config.memslab, KOBJMEMSLAB) ownership/type check or the blocksize sanity check. The memslab field is a struct kmemslab , so on that path a completely unvalidated, user-chosen pointer reaches driver code that is entitled to trust it as a real kernel object.

Any thread running under CONFIGUSERSPACE that has access to an I2S device object can trigger this with a single i2sconfigure() call. Most in-tree drivers special-case frameclkfreq == 0 and never store the caller's structure, but drivers/i2s/i2slitex.c does not: i2slitexconfigure() only logs a warning for a mismatched frame clock, then memcpy()s the caller's config into stream->cfg and marks the stream I2SSTATEREADY. A subsequent i2strigger() with I2STRIGGERSTART enables the FIFO interrupt, and the interrupt handlers i2slitexisrrx() / i2slitexisrtx() then call kmemslaballoc() and kmemslabfree() on the attacker-supplied pointer in supervisor context.

The result is an arbitrary-pointer read/write primitive executed by the kernel on behalf of an unprivileged thread — kmemslaballoc() takes a spinlock at slab->lock and reads and writes slab->freelist and the link word inside the returned "block" — which defeats the CONFIGUSERSPACE isolation boundary entirely. The same missing validation is a hazard for out-of-tree I2S drivers that retain the configuration on the unconfigure path. Builds without CONFIGUSERSPACE are unaffected, since the verifier is not used.

The fix zeroes config.memslab and config.blocksize on the unconfigure path so the driver can never receive an unvalidated pointer, while still allowing a stream to be unconfigured by a caller that does not own a memory slab, as the API documents.

First published (updated )
Severity
7.8
AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H

The user-mode syscall verifier zvrfyi2sconfigure() in drivers/i2s/i2shandlers.c copies the caller's struct i2sconfig in from user memory and then, when cfg->frameclkfreq is 0 (the API's "unconfigure the stream" request), jumps straight to zimpli2sconfigure() without performing the KSYSCALLOBJ(config.memslab, KOBJMEMSLAB) ownership/type check or the blocksize sanity check. The memslab field is a struct kmemslab , so on that path a completely unvalidated, user-chosen pointer reaches driver code that is entitled to trust it as a real kernel object.

Any thread running under CONFIGUSERSPACE that has access to an I2S device object can trigger this with a single i2sconfigure() call. Most in-tree drivers special-case frameclkfreq == 0 and never store the caller's structure, but drivers/i2s/i2slitex.c does not: i2slitexconfigure() only logs a warning for a mismatched frame clock, then memcpy()s the caller's config into stream->cfg and marks the stream I2SSTATEREADY. A subsequent i2strigger() with I2STRIGGERSTART enables the FIFO interrupt, and the interrupt handlers i2slitexisrrx() / i2slitexisrtx() then call kmemslaballoc() and kmemslabfree() on the attacker-supplied pointer in supervisor context.

The result is an arbitrary-pointer read/write primitive executed by the kernel on behalf of an unprivileged thread — kmemslaballoc() takes a spinlock at slab->lock and reads and writes slab->freelist and the link word inside the returned "block" — which defeats the CONFIGUSERSPACE isolation boundary entirely. The same missing validation is a hazard for out-of-tree I2S drivers that retain the configuration on the unconfigure path. Builds without CONFIGUSERSPACE are unaffected, since the verifier is not used.

The fix zeroes config.memslab and config.blocksize on the unconfigure path so the driver can never receive an unvalidated pointer, while still allowing a stream to be unconfigured by a caller that does not own a memory slab, as the API documents.

First published (updated )
Severity
7
AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

The user-mode verification handler for the i3ctransfer() system call, zvrfyi3ctransfer() in drivers/i3c/i3chandlers.c, validated the caller-supplied struct i3cdevicedesc in place instead of snapshotting it. It confirmed only that the descriptor was readable by the calling thread (KSYSCALLMEMORYREAD()), read target->bus once to check it with KSYSCALLOBJ(), and then passed the caller's live pointer down. The implementation zimpli3ctransfer() re-reads target->bus from that user memory and dispatches through it as DEVICEAPIGET(i3c, target->bus)->i3cxfers(...), so the bus that was validated is not necessarily the bus that is used. DEVICEAPIGET() performs no runtime type check outside CONFIGDEVICEAPIASSERT builds.

On a build with CONFIGUSERSPACE and an I3C controller driver, a user thread that has been granted an I3C device object can exploit this with a second thread in the same memory domain: one thread calls i3ctransfer() while the other rewrites the bus field of the descriptor. The window between the check and the use spans the nummsgs validation, the message-array access check, a stack VLA allocation and a memcpy() of up to 32 struct i3cmsg, so the race is repeatable and can simply be retried until it lands.

When the substitution succeeds, the kernel dereferences an attacker-chosen pointer as a struct device, loads a function pointer from the attacker-controlled api table and calls it in supervisor mode with attacker-influenced arguments. This yields kernel-mode code execution from an unprivileged user thread — a complete escape of the userspace memory-protection sandbox; lesser outcomes are an arbitrary two-level kernel read or a kernel fault. Builds without CONFIGUSERSPACE, or without an I3C driver, are unaffected.

The fix copies the descriptor into a kernel-stack local with kusermodefromcopy(), validates the bus in that copy, and dispatches on the copy, so the validated value is provably the value used.

First published (updated )
Severity
7
AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

The user-mode verification handler for the i3ctransfer() system call, zvrfyi3ctransfer() in drivers/i3c/i3chandlers.c, validated the caller-supplied struct i3cdevicedesc in place instead of snapshotting it. It confirmed only that the descriptor was readable by the calling thread (KSYSCALLMEMORYREAD()), read target->bus once to check it with KSYSCALLOBJ(), and then passed the caller's live pointer down. The implementation zimpli3ctransfer() re-reads target->bus from that user memory and dispatches through it as DEVICEAPIGET(i3c, target->bus)->i3cxfers(...), so the bus that was validated is not necessarily the bus that is used. DEVICEAPIGET() performs no runtime type check outside CONFIGDEVICEAPIASSERT builds.

On a build with CONFIGUSERSPACE and an I3C controller driver, a user thread that has been granted an I3C device object can exploit this with a second thread in the same memory domain: one thread calls i3ctransfer() while the other rewrites the bus field of the descriptor. The window between the check and the use spans the nummsgs validation, the message-array access check, a stack VLA allocation and a memcpy() of up to 32 struct i3cmsg, so the race is repeatable and can simply be retried until it lands.

When the substitution succeeds, the kernel dereferences an attacker-chosen pointer as a struct device, loads a function pointer from the attacker-controlled api table and calls it in supervisor mode with attacker-influenced arguments. This yields kernel-mode code execution from an unprivileged user thread — a complete escape of the userspace memory-protection sandbox; lesser outcomes are an arbitrary two-level kernel read or a kernel fault. Builds without CONFIGUSERSPACE, or without an I3C driver, are unaffected.

The fix copies the descriptor into a kernel-stack local with kusermodefromcopy(), validates the bus in that copy, and dispatches on the copy, so the validated value is provably the value used.

First published (updated )
Severity
5.7
AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

The Bluetooth Classic AVDTP layer defers releasing a session's stream endpoints: btavdtpl2capdisconnected() in subsys/bluetooth/host/classic/avdtp.c ends with kworksubmit(&session->releasework), placing the embedded struct kwork on the system work queue's pending list. When the same struct btavdtp object is immediately reused for a new signalling connection, the session memory is re-initialized — a2dpgetconnection() in subsys/bluetooth/host/classic/a2dp.c memset()s the whole struct bta2dp, and btavdtpl2capaccept() / btavdtpconnect() call kworkinit() on session->releasework — while that work item is still linked into ksysworkq.pending. Zeroing a linked syssnodet truncates the list while its tail pointer keeps pointing past the truncation.

The reuse window is deterministic rather than a race. All host RX processing runs on the Bluetooth RX work queue at a cooperative priority (CONFIGBTRXPRIO), while releasework is submitted to the lower-priority system work queue, so the release handler cannot run until the RX thread blocks. Because l2capbrrecv() processes every signalling command contained in a single L2CAP signalling PDU back-to-back, a peer can send one PDU holding an L2CAP Disconnection Request for the AVDTP signalling channel followed by a Connection Request for PSM 0x0019; the second command reaches the session-reuse path with the release work provably still queued. The peer must already hold an encrypted AVDTP signalling channel, i.e. be a paired/bonded BR/EDR device.

The result is corruption of the global system work queue's pending list: either the queue silently stops executing any work at all (subsequent submissions are appended to a chain the head can no longer reach, and the orphaned items keep their KWORKQUEUED flag forever) or, in the tail variant, the list acquires a self-referencing node that the queue thread spins on. The written values are pointers to other struct kwork objects and are not attacker-controlled, so the impact is a device-wide, reboot-required denial of service rather than a controllable write primitive. Only builds with CONFIGBTCLASSIC and CONFIGBTA2DP are affected.

The fix is a two-commit unit, and neither commit closes the path on its own. ec822ceed83b adds a pending-release check to btavdtpconnect() and btavdtpl2capaccept(), but the A2DP upper layer's a2dpgetconnection() memset()s the session — including the queued work item's list node and flags — before those checks run, so kworkbusyget() would observe an already-cleared state. 2bd5ea88c69b closes that by making a2dpgetconnection() itself skip the reuse and return NULL while the release work is still pending, which both callers already handle by rejecting the connection attempt. Apply both commits together; either one alone leaves the device vulnerable.

First published (updated )
Severity
5.7
AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

The Bluetooth Classic AVDTP layer defers releasing a session's stream endpoints: btavdtpl2capdisconnected() in subsys/bluetooth/host/classic/avdtp.c ends with kworksubmit(&session->releasework), placing the embedded struct kwork on the system work queue's pending list. When the same struct btavdtp object is immediately reused for a new signalling connection, the session memory is re-initialized — a2dpgetconnection() in subsys/bluetooth/host/classic/a2dp.c memset()s the whole struct bta2dp, and btavdtpl2capaccept() / btavdtpconnect() call kworkinit() on session->releasework — while that work item is still linked into ksysworkq.pending. Zeroing a linked syssnodet truncates the list while its tail pointer keeps pointing past the truncation.

The reuse window is deterministic rather than a race. All host RX processing runs on the Bluetooth RX work queue at a cooperative priority (CONFIGBTRXPRIO), while releasework is submitted to the lower-priority system work queue, so the release handler cannot run until the RX thread blocks. Because l2capbrrecv() processes every signalling command contained in a single L2CAP signalling PDU back-to-back, a peer can send one PDU holding an L2CAP Disconnection Request for the AVDTP signalling channel followed by a Connection Request for PSM 0x0019; the second command reaches the session-reuse path with the release work provably still queued. The peer must already hold an encrypted AVDTP signalling channel, i.e. be a paired/bonded BR/EDR device.

The result is corruption of the global system work queue's pending list: either the queue silently stops executing any work at all (subsequent submissions are appended to a chain the head can no longer reach, and the orphaned items keep their KWORKQUEUED flag forever) or, in the tail variant, the list acquires a self-referencing node that the queue thread spins on. The written values are pointers to other struct kwork objects and are not attacker-controlled, so the impact is a device-wide, reboot-required denial of service rather than a controllable write primitive. Only builds with CONFIGBTCLASSIC and CONFIGBTA2DP are affected.

The fix is a two-commit unit, and neither commit closes the path on its own. ec822ceed83b adds a pending-release check to btavdtpconnect() and btavdtpl2capaccept(), but the A2DP upper layer's a2dpgetconnection() memset()s the session — including the queued work item's list node and flags — before those checks run, so kworkbusyget() would observe an already-cleared state. 2bd5ea88c69b closes that by making a2dpgetconnection() itself skip the reuse and return NULL while the release work is still pending, which both callers already handle by rejecting the connection attempt. Apply both commits together; either one alone leaves the device vulnerable.

First published (updated )
Severity
8.8
Use After Free
AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

The canaddrxfiltermsgq() system call installs a CAN RX filter whose callback (canmsgqput() in drivers/can/cancommon.c) is handed the caller's struct kmsgq as userdata. The controller driver stores that raw pointer in its filter table and dereferences it from the RX path every time a matching frame arrives. The system call verifier zvrfycanaddrxfiltermsgq() in drivers/can/canhandlers.c validated the queue only with KSYSCALLOBJ(), which is a point-in-time check of type, initialization and caller permission and says nothing about the object's lifetime.

On a build with CONFIGUSERSPACE=y and CONFIGDYNAMICOBJECTS=y, an unprivileged user-mode thread that has been granted access to a CAN device can obtain a message queue whose lifetime it fully controls: kobjectalloc(KOBJMSGQ) followed by kmsgqallocinit(). The lifetime of such an object is driven solely by its permission bitmask, and a pointer retained by the kernel is not represented there. After registering the filter, the thread calls kobjectrelease() — or simply terminates — and unrefcheck() frees the struct kmsgq storage while the CAN driver still holds the pointer.

The next matching CAN frame causes kmsgqput() to run in supervisor context against freed memory that the attacker can have groomed with another kernel allocation: the frame payload is memcpy()d through the reused block's writeptr, and the queue's waitq (and pollevents under CONFIGPOLL) list nodes are manipulated. This yields kernel-heap corruption reachable from a user-mode thread, defeating the isolation CONFIGUSERSPACE is intended to provide; the same dangling pointer also arises benignly when an application thread exits without calling canremoverxfilter().

The fix validates the queue with KSYSCALLOBJSTATIC(), which additionally rejects objects carrying KOBJFLAGALLOC, so only statically defined queues (CANMSGQDEFINE()) may be registered from user mode. Supervisor-mode callers bypass the verifier and must themselves keep the queue valid until the filter is removed.

First published (updated )
Severity
6.7
AV:A/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:H

The Bluetooth host's GATT client passed the value of a received Handle Value Notification, Handle Value Indication or Multiple Handle Value Notification to btgattsubscribeparams::notify callbacks using only the length derived from the received ATT PDU. btattrecv() in subsys/bluetooth/host/att.c validates only a minimum PDU length and does not enforce the negotiated ATT MTU, and the fixed ATT L2CAP channel in subsys/bluetooth/host/l2cap.c applies no rx.mtu check either, so the only ceiling was the ACL reassembly buffer CONFIGBTBUFACLRXSIZE. Values longer than the 512-octet maximum attribute length defined by the Bluetooth Core Specification (Vol 3, Part F, Section 3.2.9) could therefore be handed to subscribers that size their buffers to that documented limit.

A remote peer that a device has connected and subscribed to — a malicious, spoofed or compromised GATT server within radio range — triggers the flaw by sending a notification or indication whose value exceeds 512 octets on a subscribed handle. In the LE Audio unicast client this reaches a concrete out-of-bounds write: unicastclientepnotify() in subsys/bluetooth/audio/bapunicastclient.c copies the notification into the 512-byte attbuf of struct unicastclient with netbufsimpleaddmem() and no tailroom check whenever length equals btaudiogetmaxntfsize(), i.e. the negotiated ATT MTU minus three. notifyhandler() in subsys/bluetooth/audio/bapbroadcastassistant.c has the same shape. Reaching the overflow requires a build whose ATT MTU exceeds 515 octets (CONFIGBTL2CAPTXMTU and CONFIGBTBUFACLRXSIZE set for full-length attribute support); the shipped defaults are far smaller.

The overflow writes peer-controlled bytes past attbuf directly over the adjacent struct netbufsimple netbuf — its data, buf, len and size fields — which the subsequent long-read accumulation then uses as its destination, turning a small linear overflow into a controlled write. netbufsimpleadd() only asserts on insufficient tailroom unless CONFIGNETBUFHARDENING is enabled, and that option is disabled by default, so ordinary production builds perform the copy. Beyond the in-tree LE Audio clients, any application subscriber that sized its receive buffer to the specification's 512-octet limit was exposed to the same overflow.

The fix rejects notification, indication and multiple-notification values longer than BTATTMAXATTRIBUTELEN in callnotifycbandmaybeunsubscribe() in subsys/bluetooth/host/gatt.c before any subscriber callback is invoked, so no subscriber can be handed more than the specified maximum. Remaining tuples of a multiple handle value notification are still delivered and oversized indications are still confirmed.

First published (updated )
Severity
4.6
Use After Free
AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L

The AVRCP Target role in subsys/bluetooth/host/classic/avrcp.c keeps its vendor-dependent response transmit state in a per-connection object selected by connection index (getavrcptg()), with the per-transfer context (tid, sentlen, and the BTAVRCPTGFLAGTXONGOING flag) stored in the userdata of the queued response netbuf. The AVCTP teardown handler avrcpdisconnected() did not cancel tg->vdrsptxwork and did not release the buffers still queued on tg->vdrsptxpending, so a link drop in the middle of a fragmented response left the buffer on the list with TXONGOING still set.

A remote AVRCP Controller can trigger this deterministically: it issues a vendor-dependent command whose response exceeds one AVRCP fragment (for example GetElementAttributes or GetCapabilities), lets btavrcptgvendortxwork() emit the START fragment, and then drops the ACL link instead of sending REQUESTCONTINUINGRESPONSE. The AVCTP control channel requires an encrypted BR/EDR link (BTSECURITYL2), so the attacker must be a paired peer within radio range; no user interaction is needed.

Because the Target objects are static and reused, the next connection landing on the same connection index finds the stale buffer at the head of vdrsptxpending. Every run of the TX work sees TXONGOING and returns immediately, so all further vendor-dependent responses queue behind the stale head and are never sent — a persistent loss of AVRCP Target function that no timeout clears and only a reboot recovers. Each cycle also leaks one response buffer from a shallow application pool, and a new peer that sends REQUESTCONTINUINGRESPONSE clears the stale TXONGOING via btavrcptgprocesscontinuingcmd(), causing the un-sent remainder of the previous peer's response to be transmitted to it.

The fix cancels tg->vdrsptxwork and unrefs every buffer on tg->vdrsptxpending in avrcpdisconnected(); since the TX context lives in the buffer userdata, releasing the buffers also resets the TX state machine. There is no memory corruption: the pre-fix buffer is leaked with a live reference, so no use-after-free arises.

First published (updated )
Severity
4.6
Use After Free
AV:A/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L

The AVRCP Target role in subsys/bluetooth/host/classic/avrcp.c keeps its vendor-dependent response transmit state in a per-connection object selected by connection index (getavrcptg()), with the per-transfer context (tid, sentlen, and the BTAVRCPTGFLAGTXONGOING flag) stored in the userdata of the queued response netbuf. The AVCTP teardown handler avrcpdisconnected() did not cancel tg->vdrsptxwork and did not release the buffers still queued on tg->vdrsptxpending, so a link drop in the middle of a fragmented response left the buffer on the list with TXONGOING still set.

A remote AVRCP Controller can trigger this deterministically: it issues a vendor-dependent command whose response exceeds one AVRCP fragment (for example GetElementAttributes or GetCapabilities), lets btavrcptgvendortxwork() emit the START fragment, and then drops the ACL link instead of sending REQUESTCONTINUINGRESPONSE. The AVCTP control channel requires an encrypted BR/EDR link (BTSECURITYL2), so the attacker must be a paired peer within radio range; no user interaction is needed.

Because the Target objects are static and reused, the next connection landing on the same connection index finds the stale buffer at the head of vdrsptxpending. Every run of the TX work sees TXONGOING and returns immediately, so all further vendor-dependent responses queue behind the stale head and are never sent — a persistent loss of AVRCP Target function that no timeout clears and only a reboot recovers. Each cycle also leaks one response buffer from a shallow application pool, and a new peer that sends REQUESTCONTINUINGRESPONSE clears the stale TXONGOING via btavrcptgprocesscontinuingcmd(), causing the un-sent remainder of the previous peer's response to be transmitted to it.

The fix cancels tg->vdrsptxwork and unrefs every buffer on tg->vdrsptxpending in avrcpdisconnected(); since the TX context lives in the buffer userdata, releasing the buffers also resets the TX state machine. There is no memory corruption: the pre-fix buffer is leaked with a live reference, so no use-after-free arises.

First published (updated )
Severity
4.8
Use After Free
AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

The MCTP over USB device class in subsys/pmci/mctp/mctpusb.c splits RX handling across two threads: the system workqueue runs mctpusbclassoutwork(), which parses host OUT data byte by byte and fills a packet buffer at usb->rxpkt, while the USB device stack thread runs mctpusbclassdisable(). The disable handler used kworkcancel(), which removes a queued work item but returns immediately without waiting for a handler that is already running, and then called mctpusbresetrxstate() to mctppktbuffree() the packet buffer the worker may still be using.

Both entry points into mctpusbclassdisable() are driven by the attached USB host: a SETCONFIGURATION request and a USB bus reset both reach it via usbdconfigreset() in subsys/usb/devicenext/usbdconfig.c. A host that streams partial MCTP frames on the bulk OUT endpoint and repeatedly forces a configuration change or bus reset therefore controls both the data being parsed and the moment the teardown runs, and can retry the race indefinitely without credentials or user interaction.

If the free lands while the worker holds the pointer, the worker writes host-supplied bytes into freed memory of the dedicated MCTP heap (CONFIGMCTPHEAPSIZE in subsys/pmci/mctp/mctpmemory.c), corrupting recycled buffers or sysheap free-block metadata; this window requires genuine parallelism, i.e. CONFIGSMP or a preemptible CONFIGSYSTEMWORKQUEUEPRIORITY. With the default cooperative workqueue the worker can still yield inside mctpbusrx() (application callback or a control-message reply blocking in mctpusbtx()), after which it resumes and calls mctppktbuffree(usb->rxpkt) on a pointer the teardown has NULLed, dereferencing NULL and faulting on targets with null-pointer detection.

The fix replaces the cancel with kworkcancelsync() plus a new struct kworksync outworksync, so disable waits for the RX worker to finish before draining the FIFO and freeing the packet buffer. The affected code is the MCTP subsystem, which is marked experimental, built only with CONFIGMCTPUSB.

First published (updated )
Severity
4.8
Use After Free
AV:P/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

The MCTP over USB device class in subsys/pmci/mctp/mctpusb.c splits RX handling across two threads: the system workqueue runs mctpusbclassoutwork(), which parses host OUT data byte by byte and fills a packet buffer at usb->rxpkt, while the USB device stack thread runs mctpusbclassdisable(). The disable handler used kworkcancel(), which removes a queued work item but returns immediately without waiting for a handler that is already running, and then called mctpusbresetrxstate() to mctppktbuffree() the packet buffer the worker may still be using.

Both entry points into mctpusbclassdisable() are driven by the attached USB host: a SETCONFIGURATION request and a USB bus reset both reach it via usbdconfigreset() in subsys/usb/devicenext/usbdconfig.c. A host that streams partial MCTP frames on the bulk OUT endpoint and repeatedly forces a configuration change or bus reset therefore controls both the data being parsed and the moment the teardown runs, and can retry the race indefinitely without credentials or user interaction.

If the free lands while the worker holds the pointer, the worker writes host-supplied bytes into freed memory of the dedicated MCTP heap (CONFIGMCTPHEAPSIZE in subsys/pmci/mctp/mctpmemory.c), corrupting recycled buffers or sysheap free-block metadata; this window requires genuine parallelism, i.e. CONFIGSMP or a preemptible CONFIGSYSTEMWORKQUEUEPRIORITY. With the default cooperative workqueue the worker can still yield inside mctpbusrx() (application callback or a control-message reply blocking in mctpusbtx()), after which it resumes and calls mctppktbuffree(usb->rxpkt) on a pointer the teardown has NULLed, dereferencing NULL and faulting on targets with null-pointer detection.

The fix replaces the cancel with kworkcancelsync() plus a new struct kworksync outworksync, so disable waits for the RX worker to finish before draining the FIFO and freeing the packet buffer. The affected code is the MCTP subsystem, which is marked experimental, built only with CONFIGMCTPUSB.

First published (updated )
Severity
6.5
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

ethernetfillheader() in subsys/net/l2/ethernet/ethernet.c can prepend the Ethernet header in place, by pushing it into the headroom of the packet's first fragment, instead of allocating a separate header fragment. Before this fix the in-place path was selected purely from the build-time value of CONFIGNETL2ETHERNETRESERVEHEADER, with no check that the packet actually has that headroom, and then called netbufpush(pkt->buffer, hdrlen). Only Ethernet TX allocations reserve that space: netpktallocbuffer() invokes the L2 alloc hook (ethernetl2alloc()) only when ispkttx() is true and the packet's interface is the Ethernet interface.

A packet that was allocated by a different L2 and then handed to Ethernet for transmission therefore has too little (usually zero) headroom. Such packets are produced by ordinary routing and tunnelling paths: netroutepacketif() re-targets a received packet to another interface when CONFIGNETIPV4FORWARDING / CONFIGNETIPV6FORWARDING are enabled (for example an 802.15.4/OpenThread or PPP interface forwarding to Wi-Fi/Ethernet), netrouteipv6mcastforwardpacket() forwards an RX-slab clone that carries no reserve, and the IPIP tunnel interfacesend() prepends an outer IP header allocated on the virtual interface. In these configurations a remote or adjacent attacker triggers the condition simply by sending traffic that the device forwards.

Because netbufsimplepush() only asserts on the headroom bound, a build without CONFIGASSERT (and without the newer CONFIGNETBUFHARDENING) moves the fragment data pointer below the start of its data block and writes 14 bytes — 18 with VLAN — of Ethernet header out of bounds. With the default fixed-size buffer pools this overwrites the tail of the neighbouring buffer's payload or memory preceding the pool array; with variable-size pools it overwrites the buffer reference count and the heap chunk header. The attacker controls neither the written bytes nor the offset, so the practical impact is memory corruption leading to a crash, packet corruption, or heap inconsistency rather than controlled code execution. The fix takes the in-place path only when netbufheadroom(pkt->buffer) is large enough and otherwise falls back to a separate header fragment.

First published (updated )
Severity
6.5
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:H

ethernetfillheader() in subsys/net/l2/ethernet/ethernet.c can prepend the Ethernet header in place, by pushing it into the headroom of the packet's first fragment, instead of allocating a separate header fragment. Before this fix the in-place path was selected purely from the build-time value of CONFIGNETL2ETHERNETRESERVEHEADER, with no check that the packet actually has that headroom, and then called netbufpush(pkt->buffer, hdrlen). Only Ethernet TX allocations reserve that space: netpktallocbuffer() invokes the L2 alloc hook (ethernetl2alloc()) only when ispkttx() is true and the packet's interface is the Ethernet interface.

A packet that was allocated by a different L2 and then handed to Ethernet for transmission therefore has too little (usually zero) headroom. Such packets are produced by ordinary routing and tunnelling paths: netroutepacketif() re-targets a received packet to another interface when CONFIGNETIPV4FORWARDING / CONFIGNETIPV6FORWARDING are enabled (for example an 802.15.4/OpenThread or PPP interface forwarding to Wi-Fi/Ethernet), netrouteipv6mcastforwardpacket() forwards an RX-slab clone that carries no reserve, and the IPIP tunnel interfacesend() prepends an outer IP header allocated on the virtual interface. In these configurations a remote or adjacent attacker triggers the condition simply by sending traffic that the device forwards.

Because netbufsimplepush() only asserts on the headroom bound, a build without CONFIGASSERT (and without the newer CONFIGNETBUFHARDENING) moves the fragment data pointer below the start of its data block and writes 14 bytes — 18 with VLAN — of Ethernet header out of bounds. With the default fixed-size buffer pools this overwrites the tail of the neighbouring buffer's payload or memory preceding the pool array; with variable-size pools it overwrites the buffer reference count and the heap chunk header. The attacker controls neither the written bytes nor the offset, so the practical impact is memory corruption leading to a crash, packet corruption, or heap inconsistency rather than controlled code execution. The fix takes the in-place path only when netbufheadroom(pkt->buffer) is large enough and otherwise falls back to a separate header fragment.

First published (updated )
Severity
7.8
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The GPIO syscall handlers in drivers/gpio/gpiohandlers.c did not validate the pin argument supplied by a user-mode caller. zvrfygpiopinconfigure(), zvrfygpiopingetconfig() and zvrfygpiopininterruptconfigure() verified only the device object (KSYSCALLDRIVERGPIO() / KSYSCALLOBJ()) and then passed the caller-supplied gpiopint straight through to zimplgpiopin() and on to the driver. The only pin check in include/zephyr/drivers/gpio.h was ASSERT(gpioportpinissupported(cfg->portpinmask, pin), ...), which compiles to nothing unless CONFIGASSERT is enabled, so in a production build no check existed at all.

GPIO drivers use pin as a shift count and as an array index on the documented assumption that it is a pin the controller supports. On STM32, for example, gpiostm32pininterruptconfigure() turns pin into an EXTI line and stm32gpiointcsetirqcallback() writes data->cb[linenum].cb and data->cb[linenum].data into a NUMEXTILINES-entry array (default 16) in drivers/interruptcontroller/intcgpiostm32.c, guarded only by a compiled-out ASSERTNOMSG(); the same function then reads extiirqtable[linenum] and calls irqenable() on the result. Other controllers index fixed-size per-pin configuration tables the same way — drivers/gpio/gpioiteit8xxx2v2.c reads an MMIO base pointer out of wucbase[8] with the caller's pin and performs a read-modify-write through it.

The issue is reachable only in builds with CONFIGUSERSPACE=y (the file is compiled under that symbol), by a user-mode thread that has been granted access to a GPIO device object. Such a grant is intended to authorize control of that port's pins; the missing validation instead lets the thread drive a supervisor-mode out-of-bounds write of a function pointer and an associated void past a kernel .bss array, and out-of-bounds reads that feed irqenable() and MMIO accesses — an escape from the user-mode sandbox that can lead to kernel memory corruption, arbitrary code execution, or a crash.

The fix validates pin against the controller's gpiodriverconfig.portpinmask with gpioportpinissupported() in every pin-taking zvrfy handler and returns -EINVAL otherwise, complemented by an unconditional pin >= GPIOMAXPINSPERPORT range check added to the shared zimplgpiopin() inlines.

First published (updated )
Severity
7.8
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The GPIO syscall handlers in drivers/gpio/gpiohandlers.c did not validate the pin argument supplied by a user-mode caller. zvrfygpiopinconfigure(), zvrfygpiopingetconfig() and zvrfygpiopininterruptconfigure() verified only the device object (KSYSCALLDRIVERGPIO() / KSYSCALLOBJ()) and then passed the caller-supplied gpiopint straight through to zimplgpiopin() and on to the driver. The only pin check in include/zephyr/drivers/gpio.h was ASSERT(gpioportpinissupported(cfg->portpinmask, pin), ...), which compiles to nothing unless CONFIGASSERT is enabled, so in a production build no check existed at all.

GPIO drivers use pin as a shift count and as an array index on the documented assumption that it is a pin the controller supports. On STM32, for example, gpiostm32pininterruptconfigure() turns pin into an EXTI line and stm32gpiointcsetirqcallback() writes data->cb[linenum].cb and data->cb[linenum].data into a NUMEXTILINES-entry array (default 16) in drivers/interruptcontroller/intcgpiostm32.c, guarded only by a compiled-out ASSERTNOMSG(); the same function then reads extiirqtable[linenum] and calls irqenable() on the result. Other controllers index fixed-size per-pin configuration tables the same way — drivers/gpio/gpioiteit8xxx2v2.c reads an MMIO base pointer out of wucbase[8] with the caller's pin and performs a read-modify-write through it.

The issue is reachable only in builds with CONFIGUSERSPACE=y (the file is compiled under that symbol), by a user-mode thread that has been granted access to a GPIO device object. Such a grant is intended to authorize control of that port's pins; the missing validation instead lets the thread drive a supervisor-mode out-of-bounds write of a function pointer and an associated void past a kernel .bss array, and out-of-bounds reads that feed irqenable() and MMIO accesses — an escape from the user-mode sandbox that can lead to kernel memory corruption, arbitrary code execution, or a crash.

The fix validates pin against the controller's gpiodriverconfig.portpinmask with gpioportpinissupported() in every pin-taking zvrfy handler and returns -EINVAL otherwise, complemented by an unconditional pin >= GPIOMAXPINSPERPORT range check added to the shared zimplgpiopin() inlines.

First published (updated )
Severity
6.5
Null Pointer Dereference
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

In the Bluetooth Classic Hands-Free Profile Audio Gateway role, hfpagcreate() in subsys/bluetooth/host/classic/hfpag.c starts an SDP discovery toward the peer, which links the embedded ag->sdpparam.node into the SDP client's pending request list in subsys/bluetooth/host/classic/sdp.c. If the subsequent btrfcommdlcconnect() call fails, bthfpagconnect() cleaned up by memset()-ing the entire AG object while that node was still linked, and while ag->slcwork could still be queued on the system work queue. An in-flight SDP discovery cannot be cancelled — btsdpdiscovercancel() is declared in the public header but has no implementation — so the request stays in the list with its func, pool and uuid pointers zeroed.

The RFCOMM channel passed to bthfpagconnect() is taken from the peer's own SDP Hands-Free service record in the normal AG flow (btsdpgetprotoparam() performs no range validation, and samples/bluetooth/classic/handsfreeag feeds the value straight through), so a remote BR/EDR device in radio range that advertises an RFCOMM channel outside the valid range 0x01..0x1e deterministically forces btrfcommdlcconnect() to fail and reach the wipe. Reading the peer's SDP record requires no pairing, and the peer can also trigger the failure by dropping the ACL or racing an RFCOMM DLC on the same DLCI at the right moment.

When the SDP client next advances its request list, sdpclientdiscover() dereferences the zeroed parameters — netbufalloc(param->pool, …) with a NULL pool and param->uuid->type — and the result-notification paths call through the NULL params->func. Zeroing a linked node also truncates the request list behind a stale tail pointer, and re-arming the freed AG slot re-appends the same node and can produce a self-looping list that hangs the Bluetooth RX thread. The impact is a crash or hang of the Bluetooth stack; the written value is a constant zero at a fixed static address, so no attacker-controlled write is available.

Only builds with CONFIGBTCLASSIC and CONFIGBTHFPAG that use the Audio Gateway role are affected.

First published (updated )
Severity
5.3
Use After Free
AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H

bthfphfconnect() in subsys/bluetooth/host/classic/hfphf.c allocates the per-connection HFP Hands-Free object with hfphfcreate(), which starts an SDP discovery by passing the object's embedded hf->sdpparam to btsdpdiscover(). That call links the caller-owned parameter block into the SDP client's request list (session->reqs in subsys/bluetooth/host/classic/sdp.c). If the subsequent btrfcommdlcconnect() then failed, the affected code wiped the entire object with (void)memset(newhf, 0, sizeof(newhf)) while that discovery was still outstanding, zeroing the syssnodet node that is still linked in the SDP client's list along with the request's func, uuid and pool pointers, and zeroing the hf->slcwork work item that may already be queued on the system workqueue. The discovery could not be retracted instead: btsdpdiscovercancel() is declared in include/zephyr/bluetooth/classic/sdp.h but has no implementation.

The failing branch is reachable with peer-supplied input. The documented use of bthfphfconnect() is to pass the RFCOMM channel read out of the remote Audio Gateway's SDP record; neither btsdpgetprotoparam() nor bthfphfconnect() range-checks that value, so a remote device advertising an HFP AG record with an RFCOMM channel outside 1-30 makes btrfcommdlcconnect() return -EINVAL and drives the local device into the wipe. The same branch is reached without any application misuse when the remote peer controls connection timing, for example when the RFCOMM session is in BTRFCOMMSTATEDISCONNECTING or the ACL is dropped between object creation and the RFCOMM call.

Once the object has been wiped, the Bluetooth host continues to use it: sdpclientallocbuf() dereferences session->param->pool, which is now NULL, and the SDP receive paths call session->param->func() through a null function pointer, neither of which is NULL-checked. Because the wipe also clears hf->acl, the pool slot is considered free and a retry or an incoming HFP connection re-appends the same still-linked list node, turning session->reqs into a self-referential list that the host's list walkers never leave. The result is a fault or an unrecoverable hang in the Bluetooth host thread; the stray indirect call is through an all-zero pointer whose target the attacker does not control, so the practical impact is denial of service.

The fix keeps the object allocated and marks it with a new BTHFPHFFLAGRELEASING flag, letting bthfphfdiscovercb() release it once the discovery completes instead of destroying it under the SDP client. Only builds with CONFIGBTCLASSIC and CONFIGBTHFPHF enabled are affected.

1 / 2
Source: NVD
First published (updated )
Severity
3.7
Weak RNG
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N

The IPv6 stack derived the long-lived secret key used to compute RFC 7217 stable interface identifiers (genstableiid() in subsys/net/ip/ipv6.c) and RFC 8981 temporary interface identifiers (gentemporaryiid() in subsys/net/ip/ipv6pe.c) with sysrandget(). That API is documented as not cryptographically secure, while both RFCs require this key to be an unpredictable random value of at least 128 bits — the unpredictability of every generated IID rests entirely on it.

Depending on the platform, sysrandget() resolves either to a hardware entropy device or, on boards whose entropy driver sets CONFIGENTROPYNEEDSPRNG, to the non-cryptographic xoshiro128++ generator in subsys/random/randomxoshiro128.c, which keeps a single global 128-bit state shared with every other sysrandget() consumer. Many of those consumers put their output directly on the wire (ephemeral source ports, DNS query IDs, CoAP message IDs and tokens, DHCPv6 transaction IDs, WebSocket masking keys), and the xoshiro state transition is invertible, so an observer of a device's traffic can recover the generator state and reconstruct the IID secret key. Additionally, with the entropy-device backend sysrandget() silently falls back to kcycleget32() samples when the entropy driver fails, and returns no error, so the key could be derived from the cycle counter without the caller noticing.

With the key known, stable IIDs become directly computable for any prefix (the remaining HMAC inputs — prefix, interface index, interface name, DAD counter — are guessable), and temporary IIDs become linkable after a cheap search over the mixed-in kuptimeget32() value. This defeats the properties both features exist to provide: an attacker can track a device across networks and prefixes, and can predict its address inside a /64 without scanning, removing the usual barrier to directly targeting the host. There is no memory-safety, integrity, or availability impact.

The fix generates the key with syscsrandget() and aborts IID generation with -EIO when no cryptographic randomness is available, instead of silently deriving addresses from a predictable key. Both CONFIGNETIPV6IIDSTABLE and CONFIGNETIPV6PE are experimental and disabled by default; builds using CONFIGTESTRANDOMGENERATOR remain predictable by design, as syscsrandget() is deliberately routed back to the test generator there.

1 / 2
Source: NVD
First published (updated )
Severity
3.7
Weak RNG
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N

The IPv6 stack derived the long-lived secret key used to compute RFC 7217 stable interface identifiers (genstableiid() in subsys/net/ip/ipv6.c) and RFC 8981 temporary interface identifiers (gentemporaryiid() in subsys/net/ip/ipv6pe.c) with sysrandget(). That API is documented as not cryptographically secure, while both RFCs require this key to be an unpredictable random value of at least 128 bits — the unpredictability of every generated IID rests entirely on it.

Depending on the platform, sysrandget() resolves either to a hardware entropy device or, on boards whose entropy driver sets CONFIGENTROPYNEEDSPRNG, to the non-cryptographic xoshiro128++ generator in subsys/random/randomxoshiro128.c, which keeps a single global 128-bit state shared with every other sysrandget() consumer. Many of those consumers put their output directly on the wire (ephemeral source ports, DNS query IDs, CoAP message IDs and tokens, DHCPv6 transaction IDs, WebSocket masking keys), and the xoshiro state transition is invertible, so an observer of a device's traffic can recover the generator state and reconstruct the IID secret key. Additionally, with the entropy-device backend sysrandget() silently falls back to kcycleget32() samples when the entropy driver fails, and returns no error, so the key could be derived from the cycle counter without the caller noticing.

With the key known, stable IIDs become directly computable for any prefix (the remaining HMAC inputs — prefix, interface index, interface name, DAD counter — are guessable), and temporary IIDs become linkable after a cheap search over the mixed-in kuptimeget32() value. This defeats the properties both features exist to provide: an attacker can track a device across networks and prefixes, and can predict its address inside a /64 without scanning, removing the usual barrier to directly targeting the host. There is no memory-safety, integrity, or availability impact.

The fix generates the key with syscsrandget() and aborts IID generation with -EIO when no cryptographic randomness is available, instead of silently deriving addresses from a predictable key. Both CONFIGNETIPV6IIDSTABLE and CONFIGNETIPV6PE are experimental and disabled by default; builds using CONFIGTESTRANDOMGENERATOR remain predictable by design, as syscsrandget() is deliberately routed back to the test generator there.

First published (updated )
Severity
7.1
AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

The Object Transfer Service server builds its Object Action Control Point (OACP) response indication in oacpindsend() (subsys/bluetooth/services/ots/otsoacp.c). Before this fix the four-byte Checksum Value field was appended whenever the requested op code was BTGATTOTSOACPPROCCHECKSUMCALC, regardless of the result code. The response payload is a bare, uninitialized four-byte stack buffer declared with NETBUFSIMPLEDEFINE(respparam, sizeof(uint32t)) in btgattotsoacpwrite(), and it is filled only on the success path of oacpchecksumprocvalidate(). On every failure path the buffer is still empty, so netbufsimplepullle32() was invoked on a zero-length buffer.

The OACP characteristic and its client characteristic configuration descriptor are declared with BTGATTPERMWRITE and BTGATTPERMREAD | BTGATTPERMWRITE, so no pairing, encryption, or authentication is required. Any peer within radio range that can connect to a device running the OTS server can enable OACP indications and write a nine-byte Calculate Checksum command that fails validation — for example by issuing it with no object selected, which returns BTGATTOTSOACPRESINVOBJ. Because oacpcommanddecode() accepts the Calculate Checksum op code unconditionally (it is not guarded by CONFIGBTOTSOACPCHECKSUMSUPPORT), builds without checksum support are reachable too: they answer BTGATTOTSOACPRESOPCODENOTSUP and take the same bad path.

On builds with CONFIGASSERT enabled, the zero-length pull trips the assertion inside netbufsimplepullmem() and panics the device, giving a remotely triggered denial of service. On builds with assertions disabled there is no check: the pull returns the untouched stack array and four bytes of uninitialized stack memory are copied into the indication and transmitted to the peer, repeatable on every request. The disclosure is bounded to four bytes per request and the write stays inside the seven-byte res indication buffer, so there is no memory corruption; the impact is uninitialized-memory disclosure plus an availability loss in assertion-enabled configurations.

1 / 2
Source: NVD
First published (updated )
Severity
7.1
AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H

The Object Transfer Service server builds its Object Action Control Point (OACP) response indication in oacpindsend() (subsys/bluetooth/services/ots/otsoacp.c). Before this fix the four-byte Checksum Value field was appended whenever the requested op code was BTGATTOTSOACPPROCCHECKSUMCALC, regardless of the result code. The response payload is a bare, uninitialized four-byte stack buffer declared with NETBUFSIMPLEDEFINE(respparam, sizeof(uint32t)) in btgattotsoacpwrite(), and it is filled only on the success path of oacpchecksumprocvalidate(). On every failure path the buffer is still empty, so netbufsimplepullle32() was invoked on a zero-length buffer.

The OACP characteristic and its client characteristic configuration descriptor are declared with BTGATTPERMWRITE and BTGATTPERMREAD | BTGATTPERMWRITE, so no pairing, encryption, or authentication is required. Any peer within radio range that can connect to a device running the OTS server can enable OACP indications and write a nine-byte Calculate Checksum command that fails validation — for example by issuing it with no object selected, which returns BTGATTOTSOACPRESINVOBJ. Because oacpcommanddecode() accepts the Calculate Checksum op code unconditionally (it is not guarded by CONFIGBTOTSOACPCHECKSUMSUPPORT), builds without checksum support are reachable too: they answer BTGATTOTSOACPRESOPCODENOTSUP and take the same bad path.

On builds with CONFIGASSERT enabled, the zero-length pull trips the assertion inside netbufsimplepullmem() and panics the device, giving a remotely triggered denial of service. On builds with assertions disabled there is no check: the pull returns the untouched stack array and four bytes of uninitialized stack memory are copied into the indication and transmitted to the peer, repeatable on every request. The disclosure is bounded to four bytes per request and the write stays inside the seven-byte res indication buffer, so there is no memory corruption; the impact is uninitialized-memory disclosure plus an availability loss in assertion-enabled configurations.

First published (updated )
Severity
4.3
AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

The Bluetooth host pre-allocates a btconn object (a "reservation") from the fixed aclconns[] pool for every connectable advertising set it starts, in leadvstartaddconn() in subsys/bluetooth/host/adv.c. When an incoming peripheral connection completes, findpendingconnect() in subsys/bluetooth/host/hcicore.c looked that reservation up using only the identity and the peer address, with a directed-then-undirected fallback, and had no knowledge of which advertising set actually terminated. With CONFIGBTEXTADVMAXADVSET > 1, where the LE Enhanced Connection Complete event is cached until the LE Advertising Set Terminated event identifies the set, this lookup can return the reservation belonging to a different, still-running advertising set.

The defect is reachable over the air by any adjacent device. If an application runs two connectable extended advertising sets on the same identity — one undirected and one directed at a peer P (the common "directed reconnect to a bonded central while still accepting new centrals" pattern) — a remote initiator whose address maps to P connecting on the undirected set causes the directed set's reservation to be consumed instead. The initiator address is chosen freely by the remote device and is not authenticated at link establishment, so no bonding, pairing or prior relationship is required to trigger the mis-attribution.

The undirected set's own reservation is then stranded: its BTADVENABLED flag has already been cleared, and leadvstopfreeconn() — the only path that returns a reservation to the pool — is reached only for sets that are still enabled, so btleextadvstop() returns early and the object is never released. Each occurrence permanently consumes one CONFIGBTMAXCONN slot until btdisable() or reboot. In application patterns that restart the directed advertising set after each disconnect, an attacker can repeat the sequence until the pool is exhausted, after which connectable advertising fails with -ENOMEM and the device can no longer accept connections. As a side effect the directed set keeps advertising without a reservation, so a genuine connection on it is dropped by bthcidisconnect(). The impact is availability only; no memory corruption or information disclosure results, and configurations with a single advertising set are unaffected.

First published (updated )
Severity
8.8
AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

The Bluetooth HCI USB transport class of the new USB device stack, subsys/usb/devicenext/class/bthci.c, copies every controller-to-host HCI packet into a USB transfer buffer before enqueuing it on an endpoint. bthcitxsyncin() allocated that buffer from bthcieppool, a fixed pool whose slots are only USBDMAXBULKMPS bytes (64 on full-speed-only builds), and then performed netbufaddmem(buf, btbuf->data, btbuf->len) with no length check. HCI event and ACL buffers are sized by CONFIGBTBUFEVTRXSIZE and CONFIGBTBUFACLRXSIZE and can be far larger, so the copy runs past the end of the 64-byte slot into the adjacent slots and into whatever follows the statically allocated pool.

The device runs the local Bluetooth controller in raw HCI mode (CONFIGBTHCIRAW), so the oversized packets originate over the air. With the HCI buffer sizes recommended for full-featured controllers, an unauthenticated attacker within Bluetooth range can supply the overflowing bytes: a connected peer that negotiates Data Length Extension can send ACL fragments up to CONFIGBTCTLRDATALENGTHMAX bytes, and an attacker that merely broadcasts extended advertising data reaches the same path through LE Extended Advertising Report events, which the controller fragments only to CONFIGBTBUFEVTRXSIZE. In the stock configuration the overflow is already reached without any radio traffic: the mandatory Read Supported Commands command-complete event is 70 bytes and overruns the 64-byte slot by six bytes on every host initialization.

Because netbufaddmem() validates tailroom only through an assertion unless CONFIGNETBUFHARDENING is enabled, builds with CONFIGASSERT=n — the usual production setting — silently corrupt memory adjacent to the USB buffer pool with attacker-chosen contents, which on a target without memory protection between subsystems can lead to arbitrary code execution. Builds with assertions enabled fail the assertion instead and panic, giving a remotely triggerable denial of service. The fix introduces a separate bthcitxpool sized to MAX(BTBUFACLRXSIZE, BTBUFEVTRXSIZE) for the controller-to-host direction while leaving the host-to-device pool at the endpoint packet size, which is already bounds-checked during reassembly.

1 / 2
Source: MITRE
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203